Passed
Branch riff-file (8e0c23)
by Rafael S.
02:48
created

WaveFile.toRIFX   A

Complexity

Conditions 1

Size

Total Lines 8
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 7
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
/*
2
 * Copyright (c) 2017-2019 Rafael da Silva Rocha.
3
 *
4
 * Permission is hereby granted, free of charge, to any person obtaining
5
 * a copy of this software and associated documentation files (the
6
 * "Software"), to deal in the Software without restriction, including
7
 * without limitation the rights to use, copy, modify, merge, publish,
8
 * distribute, sublicense, and/or sell copies of the Software, and to
9
 * permit persons to whom the Software is furnished to do so, subject to
10
 * the following conditions:
11
 *
12
 * The above copyright notice and this permission notice shall be
13
 * included in all copies or substantial portions of the Software.
14
 *
15
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
 *
23
 */
24
25
/**
26
 * @fileoverview The WaveFile class.
27
 * @see https://github.com/rochars/wavefile
28
 */
29
30
/** @module wavefile */
31
32
import bitDepthLib from 'bitdepth';
33
import * as imaadpcm from 'imaadpcm';
34
import * as alawmulaw from 'alawmulaw';
35
import {encode, decode} from 'base64-arraybuffer-es6';
36
import RIFFFile from './lib/riff-file';
37
import writeString from './lib/write-string';
38
import dwChannelMask from './lib/dw-channel-mask';
39
import interleave from './lib/interleave';
40
import {unpackArray, packArrayTo, unpackArrayTo,
41
  unpack, packTo, packStringTo, packString, pack} from 'byte-data';
42
43
/**
44
 * A class to read, write and process wav files.
45
 */
46
export default class WaveFile extends RIFFFile {
47
48
  /**
49
   * @param {?Uint8Array=} wavBuffer A wave file buffer.
50
   * @throws {Error} If no 'RIFF' chunk is found.
51
   * @throws {Error} If no 'fmt ' chunk is found.
52
   * @throws {Error} If no 'data' chunk is found.
53
   */
54
  constructor(wavBuffer=null) {
55
    super();
56
    /**
57
     * Audio formats.
58
     * Formats not listed here should be set to 65534,
59
     * the code for WAVE_FORMAT_EXTENSIBLE
60
     * @enum {number}
61
     * @private
62
     */
63
    this.WAV_AUDIO_FORMATS = {
64
      '4': 17,
65
      '8': 1,
66
      '8a': 6,
67
      '8m': 7,
68
      '16': 1,
69
      '24': 1,
70
      '32': 1,
71
      '32f': 3,
72
      '64': 3
73
    };
74
    /**
75
     * The data of the 'fmt' chunk.
76
     * @type {!Object<string, *>}
77
     */
78
    this.fmt = {
79
      /** @type {string} */
80
      chunkId: '',
81
      /** @type {number} */
82
      chunkSize: 0,
83
      /** @type {number} */
84
      audioFormat: 0,
85
      /** @type {number} */
86
      numChannels: 0,
87
      /** @type {number} */
88
      sampleRate: 0,
89
      /** @type {number} */
90
      byteRate: 0,
91
      /** @type {number} */
92
      blockAlign: 0,
93
      /** @type {number} */
94
      bitsPerSample: 0,
95
      /** @type {number} */
96
      cbSize: 0,
97
      /** @type {number} */
98
      validBitsPerSample: 0,
99
      /** @type {number} */
100
      dwChannelMask: 0,
101
      /**
102
       * 4 32-bit values representing a 128-bit ID
103
       * @type {!Array<number>}
104
       */
105
      subformat: []
106
    };
107
    /**
108
     * The data of the 'fact' chunk.
109
     * @type {!Object<string, *>}
110
     */
111
    this.fact = {
112
      /** @type {string} */
113
      chunkId: '',
114
      /** @type {number} */
115
      chunkSize: 0,
116
      /** @type {number} */
117
      dwSampleLength: 0
118
    };
119
    /**
120
     * The data of the 'cue ' chunk.
121
     * @type {!Object<string, *>}
122
     */
123
    this.cue = {
124
      /** @type {string} */
125
      chunkId: '',
126
      /** @type {number} */
127
      chunkSize: 0,
128
      /** @type {number} */
129
      dwCuePoints: 0,
130
      /** @type {!Array<!Object>} */
131
      points: [],
132
    };
133
    /**
134
     * The data of the 'smpl' chunk.
135
     * @type {!Object<string, *>}
136
     */
137
    this.smpl = {
138
      /** @type {string} */
139
      chunkId: '',
140
      /** @type {number} */
141
      chunkSize: 0,
142
      /** @type {number} */
143
      dwManufacturer: 0,
144
      /** @type {number} */
145
      dwProduct: 0,
146
      /** @type {number} */
147
      dwSamplePeriod: 0,
148
      /** @type {number} */
149
      dwMIDIUnityNote: 0,
150
      /** @type {number} */
151
      dwMIDIPitchFraction: 0,
152
      /** @type {number} */
153
      dwSMPTEFormat: 0,
154
      /** @type {number} */
155
      dwSMPTEOffset: 0,
156
      /** @type {number} */
157
      dwNumSampleLoops: 0,
158
      /** @type {number} */
159
      dwSamplerData: 0,
160
      /** @type {!Array<!Object>} */
161
      loops: []
162
    };
163
    /**
164
     * The data of the 'bext' chunk.
165
     * @type {!Object<string, *>}
166
     */
167
    this.bext = {
168
      /** @type {string} */
169
      chunkId: '',
170
      /** @type {number} */
171
      chunkSize: 0,
172
      /** @type {string} */
173
      description: '', //256
174
      /** @type {string} */
175
      originator: '', //32
176
      /** @type {string} */
177
      originatorReference: '', //32
178
      /** @type {string} */
179
      originationDate: '', //10
180
      /** @type {string} */
181
      originationTime: '', //8
182
      /**
183
       * 2 32-bit values, timeReference high and low
184
       * @type {!Array<number>}
185
       */
186
      timeReference: [0, 0],
187
      /** @type {number} */
188
      version: 0, //WORD
189
      /** @type {string} */
190
      UMID: '', // 64 chars
191
      /** @type {number} */
192
      loudnessValue: 0, //WORD
193
      /** @type {number} */
194
      loudnessRange: 0, //WORD
195
      /** @type {number} */
196
      maxTruePeakLevel: 0, //WORD
197
      /** @type {number} */
198
      maxMomentaryLoudness: 0, //WORD
199
      /** @type {number} */
200
      maxShortTermLoudness: 0, //WORD
201
      /** @type {string} */
202
      reserved: '', //180
203
      /** @type {string} */
204
      codingHistory: '' // string, unlimited
205
    };
206
    /**
207
     * The data of the 'ds64' chunk.
208
     * Used only with RF64 files.
209
     * @type {!Object<string, *>}
210
     */
211
    this.ds64 = {
212
      /** @type {string} */
213
      chunkId: '',
214
      /** @type {number} */
215
      chunkSize: 0,
216
      /** @type {number} */
217
      riffSizeHigh: 0, // DWORD
218
      /** @type {number} */
219
      riffSizeLow: 0, // DWORD
220
      /** @type {number} */
221
      dataSizeHigh: 0, // DWORD
222
      /** @type {number} */
223
      dataSizeLow: 0, // DWORD
224
      /** @type {number} */
225
      originationTime: 0, // DWORD
226
      /** @type {number} */
227
      sampleCountHigh: 0, // DWORD
228
      /** @type {number} */
229
      sampleCountLow: 0 // DWORD
230
      /** @type {number} */
231
      //'tableLength': 0, // DWORD
232
      /** @type {!Array<number>} */
233
      //'table': []
234
    };
235
    /**
236
     * The data of the 'data' chunk.
237
     * @type {!Object<string, *>}
238
     */
239
    this.data = {
240
      /** @type {string} */
241
      chunkId: '',
242
      /** @type {number} */
243
      chunkSize: 0,
244
      /** @type {!Uint8Array} */
245
      samples: new Uint8Array(0)
246
    };
247
    /**
248
     * The data of the 'LIST' chunks.
249
     * Each item in this list look like this:
250
     *  {
251
     *      chunkId: '',
252
     *      chunkSize: 0,
253
     *      format: '',
254
     *      subChunks: []
255
     *   }
256
     * @type {!Array<!Object>}
257
     */
258
    this.LIST = [];
259
    /**
260
     * The data of the 'junk' chunk.
261
     * @type {!Object<string, *>}
262
     */
263
    this.junk = {
264
      /** @type {string} */
265
      chunkId: '',
266
      /** @type {number} */
267
      chunkSize: 0,
268
      /** @type {!Array<number>} */
269
      chunkData: []
270
    };
271
    /**
272
     * The bit depth code according to the samples.
273
     * @type {string}
274
     */
275
    this.bitDepth = '0';
276
    /**
277
     * @type {!Object}
278
     * @private
279
     */
280
    this.dataType = {};
281
    // Load a file from the buffer if one was passed
282
    // when creating the object
283
    if (wavBuffer) {
284
      this.fromBuffer(wavBuffer);
285
    }
286
  }
287
288
  /**
289
   * Return the sample at a given index.
290
   * @param {number} index The sample index.
291
   * @return {number} The sample.
292
   * @throws {Error} If the sample index is off range.
293
   */
294
  getSample(index) {
295
    index = index * (this.dataType.bits / 8);
296
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
297
      throw new Error('Range error');
298
    }
299
    return unpack(
300
      this.data.samples.slice(index, index + this.dataType.bits / 8),
301
      this.dataType);
302
  }
303
304
  /**
305
   * Set the sample at a given index.
306
   * @param {number} index The sample index.
307
   * @param {number} sample The sample.
308
   * @throws {Error} If the sample index is off range.
309
   */
310
  setSample(index, sample) {
311
    index = index * (this.dataType.bits / 8);
312
    if (index + this.dataType.bits / 8 > this.data.samples.length) {
313
      throw new Error('Range error');
314
    }
315
    packTo(sample, this.dataType, this.data.samples, index);
316
  }
317
318
  /**
319
   * Set up the WaveFile object based on the arguments passed.
320
   * @param {number} numChannels The number of channels
321
   *    (Integer numbers: 1 for mono, 2 stereo and so on).
322
   * @param {number} sampleRate The sample rate.
323
   *    Integer numbers like 8000, 44100, 48000, 96000, 192000.
324
   * @param {string} bitDepthCode The audio bit depth code.
325
   *    One of '4', '8', '8a', '8m', '16', '24', '32', '32f', '64'
326
   *    or any value between '8' and '32' (like '12').
327
   * @param {!Array<number>|!Array<!Array<number>>|!TypedArray} samples
328
   *    The samples. Must be in the correct range according to the bit depth.
329
   * @param {?Object} options Optional. Used to force the container
330
   *    as RIFX with {'container': 'RIFX'}
331
   * @throws {Error} If any argument does not meet the criteria.
332
   */
333
  fromScratch(numChannels, sampleRate, bitDepthCode, samples, options={}) {
334
    if (!options.container) {
335
      options.container = 'RIFF';
336
    }
337
    this.container = options.container;
338
    this.bitDepth = bitDepthCode;
339
    samples = interleave(samples);
340
    this.updateDataType_();
341
    /** @type {number} */
342
    let numBytes = this.dataType.bits / 8;
343
    this.data.samples = new Uint8Array(samples.length * numBytes);
344
    packArrayTo(samples, this.dataType, this.data.samples);
345
    this.clearHeader_();
346
    this.makeWavHeader(
347
      bitDepthCode, numChannels, sampleRate,
348
      numBytes, this.data.samples.length, options);
349
    this.data.chunkId = 'data';
350
    this.data.chunkSize = this.data.samples.length;
351
    this.validateWavHeader_();
352
  }
353
354
  /**
355
   * Set up the WaveFile object from a byte buffer.
356
   * @param {!Uint8Array} bytes The buffer.
357
   * @param {boolean=} samples True if the samples should be loaded.
358
   * @throws {Error} If container is not RIFF, RIFX or RF64.
359
   * @throws {Error} If no 'fmt ' chunk is found.
360
   * @throws {Error} If no 'data' chunk is found.
361
   */
362
  fromBuffer(bytes, samples=true) {
363
    this.clearHeader_();
364
    this.readWavBuffer(bytes, samples);
365
    this.bitDepthFromFmt_();
366
    this.updateDataType_();
367
  }
368
369
  /**
370
   * Return a byte buffer representig the WaveFile object as a .wav file.
371
   * The return value of this method can be written straight to disk.
372
   * @return {!Uint8Array} A .wav file.
373
   * @throws {Error} If any property of the object appears invalid.
374
   */
375
  toBuffer() {
376
    this.validateWavHeader_();
377
    return this.writeWavBuffer();
378
  }
379
380
  /**
381
   * Use a .wav file encoded as a base64 string to load the WaveFile object.
382
   * @param {string} base64String A .wav file as a base64 string.
383
   * @throws {Error} If any property of the object appears invalid.
384
   */
385
  fromBase64(base64String) {
386
    this.fromBuffer(new Uint8Array(decode(base64String)));
387
  }
388
389
  /**
390
   * Return a base64 string representig the WaveFile object as a .wav file.
391
   * @return {string} A .wav file as a base64 string.
392
   * @throws {Error} If any property of the object appears invalid.
393
   */
394
  toBase64() {
395
    /** @type {!Uint8Array} */
396
    let buffer = this.toBuffer();
397
    return encode(buffer, 0, buffer.length);
398
  }
399
400
  /**
401
   * Return a DataURI string representig the WaveFile object as a .wav file.
402
   * The return of this method can be used to load the audio in browsers.
403
   * @return {string} A .wav file as a DataURI.
404
   * @throws {Error} If any property of the object appears invalid.
405
   */
406
  toDataURI() {
407
    return 'data:audio/wav;base64,' + this.toBase64();
408
  }
409
410
  /**
411
   * Use a .wav file encoded as a DataURI to load the WaveFile object.
412
   * @param {string} dataURI A .wav file as DataURI.
413
   * @throws {Error} If any property of the object appears invalid.
414
   */
415
  fromDataURI(dataURI) {
416
    this.fromBase64(dataURI.replace('data:audio/wav;base64,', ''));
417
  }
418
419
  /**
420
   * Force a file as RIFF.
421
   */
422
  toRIFF() {
423
    this.fromScratch(
424
      this.fmt.numChannels,
425
      this.fmt.sampleRate,
426
      this.bitDepth,
427
      unpackArray(this.data.samples, this.dataType));
428
  }
429
430
  /**
431
   * Force a file as RIFX.
432
   */
433
  toRIFX() {
434
    this.fromScratch(
435
      this.fmt.numChannels,
436
      this.fmt.sampleRate,
437
      this.bitDepth,
438
      unpackArray(this.data.samples, this.dataType),
439
      {container: 'RIFX'});
440
  }
441
442
  /**
443
   * Change the bit depth of the samples.
444
   * @param {string} newBitDepth The new bit depth of the samples.
445
   *    One of '8' ... '32' (integers), '32f' or '64' (floats)
446
   * @param {boolean} changeResolution A boolean indicating if the
447
   *    resolution of samples should be actually changed or not.
448
   * @throws {Error} If the bit depth is not valid.
449
   */
450
  toBitDepth(newBitDepth, changeResolution=true) {
451
    /** @type {string} */
452
    let toBitDepth = newBitDepth;
453
    /** @type {string} */
454
    let thisBitDepth = this.bitDepth;
455
    if (!changeResolution) {
456
      if (newBitDepth != '32f') {
457
        toBitDepth = this.dataType.bits.toString();
458
      }
459
      thisBitDepth = this.dataType.bits;
460
    }
461
    this.assureUncompressed_();
462
    /** @type {number} */
463
    let sampleCount = this.data.samples.length / (this.dataType.bits / 8);
464
    /** @type {!Float64Array} */
465
    let typedSamplesInput = new Float64Array(sampleCount);
466
    /** @type {!Float64Array} */
467
    let typedSamplesOutput = new Float64Array(sampleCount);
468
    unpackArrayTo(this.data.samples, this.dataType, typedSamplesInput);
469
    if (thisBitDepth == "32f" || thisBitDepth == "64") {
470
      this.truncateSamples_(typedSamplesInput);
471
    }
472
    bitDepthLib(
473
      typedSamplesInput, thisBitDepth, toBitDepth, typedSamplesOutput);
474
    this.fromScratch(
475
      this.fmt.numChannels,
476
      this.fmt.sampleRate,
477
      newBitDepth,
478
      typedSamplesOutput,
479
      {container: this.correctContainer_()});
480
  }
481
482
  /**
483
   * Encode a 16-bit wave file as 4-bit IMA ADPCM.
484
   * @throws {Error} If sample rate is not 8000.
485
   * @throws {Error} If number of channels is not 1.
486
   */
487
  toIMAADPCM() {
488
    if (this.fmt.sampleRate !== 8000) {
489
      throw new Error(
490
        'Only 8000 Hz files can be compressed as IMA-ADPCM.');
491
    } else if (this.fmt.numChannels !== 1) {
492
      throw new Error(
493
        'Only mono files can be compressed as IMA-ADPCM.');
494
    } else {
495
      this.assure16Bit_();
496
      /** @type {!Int16Array} */
497
      let output = new Int16Array(this.data.samples.length / 2);
498
      unpackArrayTo(this.data.samples, this.dataType, output);
499
      this.fromScratch(
500
        this.fmt.numChannels,
501
        this.fmt.sampleRate,
502
        '4',
503
        imaadpcm.encode(output),
504
        {container: this.correctContainer_()});
505
    }
506
  }
507
508
  /**
509
   * Decode a 4-bit IMA ADPCM wave file as a 16-bit wave file.
510
   * @param {string} bitDepthCode The new bit depth of the samples.
511
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
512
   *    Optional. Default is 16.
513
   */
514
  fromIMAADPCM(bitDepthCode='16') {
515
    this.fromScratch(
516
      this.fmt.numChannels,
517
      this.fmt.sampleRate,
518
      '16',
519
      imaadpcm.decode(this.data.samples, this.fmt.blockAlign),
520
      {container: this.correctContainer_()});
521
    if (bitDepthCode != '16') {
522
      this.toBitDepth(bitDepthCode);
523
    }
524
  }
525
526
  /**
527
   * Encode a 16-bit wave file as 8-bit A-Law.
528
   */
529
  toALaw() {
530
    this.assure16Bit_();
531
    /** @type {!Int16Array} */
532
    let output = new Int16Array(this.data.samples.length / 2);
533
    unpackArrayTo(this.data.samples, this.dataType, output);
534
    this.fromScratch(
535
      this.fmt.numChannels,
536
      this.fmt.sampleRate,
537
      '8a',
538
      alawmulaw.alaw.encode(output),
539
      {container: this.correctContainer_()});
540
  }
541
542
  /**
543
   * Decode a 8-bit A-Law wave file into a 16-bit wave file.
544
   * @param {string} bitDepthCode The new bit depth of the samples.
545
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
546
   *    Optional. Default is 16.
547
   */
548
  fromALaw(bitDepthCode='16') {
549
    this.fromScratch(
550
      this.fmt.numChannels,
551
      this.fmt.sampleRate,
552
      '16',
553
      alawmulaw.alaw.decode(this.data.samples),
554
      {container: this.correctContainer_()});
555
    if (bitDepthCode != '16') {
556
      this.toBitDepth(bitDepthCode);
557
    }
558
  }
559
560
  /**
561
   * Encode 16-bit wave file as 8-bit mu-Law.
562
   */
563
  toMuLaw() {
564
    this.assure16Bit_();
565
    /** @type {!Int16Array} */
566
    let output = new Int16Array(this.data.samples.length / 2);
567
    unpackArrayTo(this.data.samples, this.dataType, output);
568
    this.fromScratch(
569
      this.fmt.numChannels,
570
      this.fmt.sampleRate,
571
      '8m',
572
      alawmulaw.mulaw.encode(output),
573
      {container: this.correctContainer_()});
574
  }
575
576
  /**
577
   * Decode a 8-bit mu-Law wave file into a 16-bit wave file.
578
   * @param {string} bitDepthCode The new bit depth of the samples.
579
   *    One of '8' ... '32' (integers), '32f' or '64' (floats).
580
   *    Optional. Default is 16.
581
   */
582
  fromMuLaw(bitDepthCode='16') {
583
    this.fromScratch(
584
      this.fmt.numChannels,
585
      this.fmt.sampleRate,
586
      '16',
587
      alawmulaw.mulaw.decode(this.data.samples),
588
      {container: this.correctContainer_()});
589
    if (bitDepthCode != '16') {
590
      this.toBitDepth(bitDepthCode);
591
    }
592
  }
593
594
  /**
595
   * Write a RIFF tag in the INFO chunk. If the tag do not exist,
596
   * then it is created. It if exists, it is overwritten.
597
   * @param {string} tag The tag name.
598
   * @param {string} value The tag value.
599
   * @throws {Error} If the tag name is not valid.
600
   */
601
  setTag(tag, value) {
602
    tag = this.fixTagName_(tag);
603
    /** @type {!Object} */
604
    let index = this.getTagIndex_(tag);
605
    if (index.TAG !== null) {
606
      this.LIST[index.LIST].subChunks[index.TAG].chunkSize =
607
        value.length + 1;
608
      this.LIST[index.LIST].subChunks[index.TAG].value = value;
609
    } else if (index.LIST !== null) {
610
      this.LIST[index.LIST].subChunks.push({
611
        chunkId: tag,
612
        chunkSize: value.length + 1,
613
        value: value});
614
    } else {
615
      this.LIST.push({
616
        chunkId: 'LIST',
617
        chunkSize: 8 + value.length + 1,
618
        format: 'INFO',
619
        subChunks: []});
620
      this.LIST[this.LIST.length - 1].subChunks.push({
621
        chunkId: tag,
622
        chunkSize: value.length + 1,
623
        value: value});
624
    }
625
  }
626
627
  /**
628
   * Return the value of a RIFF tag in the INFO chunk.
629
   * @param {string} tag The tag name.
630
   * @return {?string} The value if the tag is found, null otherwise.
631
   */
632
  getTag(tag) {
633
    /** @type {!Object} */
634
    let index = this.getTagIndex_(tag);
635
    if (index.TAG !== null) {
636
      return this.LIST[index.LIST].subChunks[index.TAG].value;
637
    }
638
    return null;
639
  }
640
641
  /**
642
   * Return a Object<tag, value> with the RIFF tags in the file.
643
   * @return {!Object<string, string>} The file tags.
644
   */
645
  listTags() {
646
    /** @type {?number} */
647
    let index = this.getLISTINFOIndex_();
648
    /** @type {!Object} */
649
    let tags = {};
650
    if (index !== null) {
651
      for (let i = 0, len = this.LIST[index].subChunks.length; i < len; i++) {
652
        tags[this.LIST[index].subChunks[i].chunkId] =
653
          this.LIST[index].subChunks[i].value;
654
      }
655
    }
656
    return tags;
657
  }
658
659
  /**
660
   * Remove a RIFF tag in the INFO chunk.
661
   * @param {string} tag The tag name.
662
   * @return {boolean} True if a tag was deleted.
663
   */
664
  deleteTag(tag) {
665
    /** @type {!Object} */
666
    let index = this.getTagIndex_(tag);
667
    if (index.TAG !== null) {
668
      this.LIST[index.LIST].subChunks.splice(index.TAG, 1);
669
      return true;
670
    }
671
    return false;
672
  }
673
674
  /**
675
   * Create a cue point in the wave file.
676
   * @param {number} position The cue point position in milliseconds.
677
   * @param {string} labl The LIST adtl labl text of the marker. Optional.
678
   */
679
  setCuePoint(position, labl='') {
680
    this.cue.chunkId = 'cue ';
681
    position = (position * this.fmt.sampleRate) / 1000;
682
    /** @type {!Array<!Object>} */
683
    let existingPoints = this.getCuePoints_();
684
    this.clearLISTadtl_();
685
    /** @type {number} */
686
    let len = this.cue.points.length;
687
    this.cue.points = [];
688
    /** @type {boolean} */
689
    let hasSet = false;
690
    if (len === 0) {
691
      this.setCuePoint_(position, 1, labl);
692
    } else {
693
      for (let i = 0; i < len; i++) {
694
        if (existingPoints[i].dwPosition > position && !hasSet) {
695
          this.setCuePoint_(position, i + 1, labl);
696
          this.setCuePoint_(
697
            existingPoints[i].dwPosition,
698
            i + 2,
699
            existingPoints[i].label);
700
          hasSet = true;
701
        } else {
702
          this.setCuePoint_(
703
            existingPoints[i].dwPosition,
704
            i + 1,
705
            existingPoints[i].label);
706
        }
707
      }
708
      if (!hasSet) {
709
        this.setCuePoint_(position, this.cue.points.length + 1, labl);
710
      }
711
    }
712
    this.cue.dwCuePoints = this.cue.points.length;
713
  }
714
715
  /**
716
   * Remove a cue point from a wave file.
717
   * @param {number} index the index of the point. First is 1,
718
   *    second is 2, and so on.
719
   */
720
  deleteCuePoint(index) {
721
    this.cue.chunkId = 'cue ';
722
    /** @type {!Array<!Object>} */
723
    let existingPoints = this.getCuePoints_();
724
    this.clearLISTadtl_();
725
    /** @type {number} */
726
    let len = this.cue.points.length;
727
    this.cue.points = [];
728
    for (let i = 0; i < len; i++) {
729
      if (i + 1 !== index) {
730
        this.setCuePoint_(
731
          existingPoints[i].dwPosition,
732
          i + 1,
733
          existingPoints[i].label);
734
      }
735
    }
736
    this.cue.dwCuePoints = this.cue.points.length;
737
    if (this.cue.dwCuePoints) {
738
      this.cue.chunkId = 'cue ';
739
    } else {
740
      this.cue.chunkId = '';
741
      this.clearLISTadtl_();
742
    }
743
  }
744
745
  /**
746
   * Return an array with all cue points in the file, in the order they appear
747
   * in the file.
748
   * The difference between this method and using the list in WaveFile.cue
749
   * is that the return value of this method includes the position in
750
   * milliseconds of each cue point (WaveFile.cue only have the sample offset)
751
   * @return {!Array<!Object>}
752
   */
753
  listCuePoints() {
754
    /** @type {!Array<!Object>} */
755
    let points = this.getCuePoints_();
756
    for (let i = 0, len = points.length; i < len; i++) {
757
      points[i].milliseconds =
758
        (points[i].dwPosition / this.fmt.sampleRate) * 1000;
759
    }
760
    return points;
761
  }
762
763
  /**
764
   * Update the label of a cue point.
765
   * @param {number} pointIndex The ID of the cue point.
766
   * @param {string} label The new text for the label.
767
   */
768
  updateLabel(pointIndex, label) {
769
    /** @type {?number} */
770
    let cIndex = this.getAdtlChunk_();
771
    if (cIndex !== null) {
772
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
773
        if (this.LIST[cIndex].subChunks[i].dwName ==
774
            pointIndex) {
775
          this.LIST[cIndex].subChunks[i].value = label;
776
        }
777
      }
778
    }
779
  }
780
781
  /**
782
   * Set the string code of the bit depth based on the 'fmt ' chunk.
783
   * @private
784
   */
785
  bitDepthFromFmt_() {
786
    if (this.fmt.audioFormat === 3 && this.fmt.bitsPerSample === 32) {
787
      this.bitDepth = '32f';
788
    } else if (this.fmt.audioFormat === 6) {
789
      this.bitDepth = '8a';
790
    } else if (this.fmt.audioFormat === 7) {
791
      this.bitDepth = '8m';
792
    } else {
793
      this.bitDepth = this.fmt.bitsPerSample.toString();
794
    }
795
  }
796
  
797
  /**
798
   * Push a new cue point in this.cue.points.
799
   * @param {number} position The position in milliseconds.
800
   * @param {number} dwName the dwName of the cue point
801
   * @private
802
   */
803
  setCuePoint_(position, dwName, label) {
804
    this.cue.points.push({
805
      dwName: dwName,
806
      dwPosition: position,
807
      fccChunk: 'data',
808
      dwChunkStart: 0,
809
      dwBlockStart: 0,
810
      dwSampleOffset: position,
811
    });
812
    this.setLabl_(dwName, label);
813
  }
814
815
  /**
816
   * Return an array with all cue points in the file, in the order they appear
817
   * in the file.
818
   * @return {!Array<!Object>}
819
   * @private
820
   */
821
  getCuePoints_() {
822
    /** @type {!Array<!Object>} */
823
    let points = [];
824
    for (let i = 0, len = this.cue.points.length; i < len; i++) {
825
      points.push({
826
        dwPosition: this.cue.points[i].dwPosition,
827
        label: this.getLabelForCuePoint_(
828
          this.cue.points[i].dwName)});
829
    }
830
    return points;
831
  }
832
833
  /**
834
   * Return the label of a cue point.
835
   * @param {number} pointDwName The ID of the cue point.
836
   * @return {string}
837
   * @private
838
   */
839
  getLabelForCuePoint_(pointDwName) {
840
    /** @type {?number} */
841
    let cIndex = this.getAdtlChunk_();
842
    if (cIndex !== null) {
843
      for (let i = 0, len = this.LIST[cIndex].subChunks.length; i < len; i++) {
844
        if (this.LIST[cIndex].subChunks[i].dwName ==
845
            pointDwName) {
846
          return this.LIST[cIndex].subChunks[i].value;
847
        }
848
      }
849
    }
850
    return '';
851
  }
852
853
  /**
854
   * Clear any LIST chunk labeled as 'adtl'.
855
   * @private
856
   */
857
  clearLISTadtl_() {
858
    for (let i = 0, len = this.LIST.length; i < len; i++) {
859
      if (this.LIST[i].format == 'adtl') {
860
        this.LIST.splice(i);
861
      }
862
    }
863
  }
864
865
  /**
866
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
867
   * @param {number} dwName The ID of the cue point.
868
   * @param {string} label The label for the cue point.
869
   * @private
870
   */
871
  setLabl_(dwName, label) {
872
    /** @type {?number} */
873
    let adtlIndex = this.getAdtlChunk_();
874
    if (adtlIndex === null) {
875
      this.LIST.push({
876
        chunkId: 'LIST',
877
        chunkSize: 4,
878
        format: 'adtl',
879
        subChunks: []});
880
      adtlIndex = this.LIST.length - 1;
881
    }
882
    this.setLabelText_(adtlIndex === null ? 0 : adtlIndex, dwName, label);
883
  }
884
885
  /**
886
   * Create a new 'labl' subchunk in a 'LIST' chunk of type 'adtl'.
887
   * @param {number} adtlIndex The index of the 'adtl' LIST in this.LIST.
888
   * @param {number} dwName The ID of the cue point.
889
   * @param {string} label The label for the cue point.
890
   * @private
891
   */
892
  setLabelText_(adtlIndex, dwName, label) {
893
    this.LIST[adtlIndex].subChunks.push({
894
      chunkId: 'labl',
895
      chunkSize: label.length,
896
      dwName: dwName,
897
      value: label
898
    });
899
    this.LIST[adtlIndex].chunkSize += label.length + 4 + 4 + 4 + 1;
900
  }
901
902
  /**
903
   * Return the index of the 'adtl' LIST in this.LIST.
904
   * @return {?number}
905
   * @private
906
   */
907
  getAdtlChunk_() {
908
    for (let i = 0, len = this.LIST.length; i < len; i++) {
909
      if (this.LIST[i].format == 'adtl') {
910
        return i;
911
      }
912
    }
913
    return null;
914
  }
915
916
  /**
917
   * Return the index of the INFO chunk in the LIST chunk.
918
   * @return {?number} the index of the INFO chunk.
919
   * @private
920
   */
921
  getLISTINFOIndex_() {
922
    /** @type {?number} */
923
    let index = null;
924
    for (let i = 0, len = this.LIST.length; i < len; i++) {
925
      if (this.LIST[i].format === 'INFO') {
926
        index = i;
927
        break;
928
      }
929
    }
930
    return index;
931
  }
932
933
  /**
934
   * Return the index of a tag in a FILE chunk.
935
   * @param {string} tag The tag name.
936
   * @return {!Object<string, ?number>}
937
   *    Object.LIST is the INFO index in LIST
938
   *    Object.TAG is the tag index in the INFO
939
   * @private
940
   */
941
  getTagIndex_(tag) {
942
    /** @type {!Object<string, ?number>} */
943
    let index = {LIST: null, TAG: null};
944
    for (let i = 0, len = this.LIST.length; i < len; i++) {
945
      if (this.LIST[i].format == 'INFO') {
946
        index.LIST = i;
947
        for (let j=0, subLen = this.LIST[i].subChunks.length; j < subLen; j++) {
948
          if (this.LIST[i].subChunks[j].chunkId == tag) {
949
            index.TAG = j;
950
            break;
951
          }
952
        }
953
        break;
954
      }
955
    }
956
    return index;
957
  }
958
959
  /**
960
   * Fix a RIFF tag format if possible, throw an error otherwise.
961
   * @param {string} tag The tag name.
962
   * @return {string} The tag name in proper fourCC format.
963
   * @private
964
   */
965
  fixTagName_(tag) {
966
    if (tag.constructor !== String) {
967
      throw new Error('Invalid tag name.');
968
    } else if (tag.length < 4) {
969
      for (let i = 0, len = 4 - tag.length; i < len; i++) {
970
        tag += ' ';
971
      }
972
    }
973
    return tag;
974
  }
975
976
  /**
977
   * Reset attributes that should emptied when a file is
978
   * created with the fromScratch() or fromBuffer() methods.
979
   * @private
980
   */
981
  clearHeader_() {
982
    this.fmt.cbSize = 0;
983
    this.fmt.validBitsPerSample = 0;
984
    this.fact.chunkId = '';
985
    this.ds64.chunkId = '';
986
  }
987
988
  /**
989
   * Make the file 16-bit if it is not.
990
   * @private
991
   */
992
  assure16Bit_() {
993
    this.assureUncompressed_();
994
    if (this.bitDepth != '16') {
995
      this.toBitDepth('16');
996
    }
997
  }
998
999
  /**
1000
   * Uncompress the samples in case of a compressed file.
1001
   * @private
1002
   */
1003
  assureUncompressed_() {
1004
    if (this.bitDepth == '8a') {
1005
      this.fromALaw();
1006
    } else if (this.bitDepth == '8m') {
1007
      this.fromMuLaw();
1008
    } else if (this.bitDepth == '4') {
1009
      this.fromIMAADPCM();
1010
    }
1011
  }
1012
1013
  /**
1014
   * Update the type definition used to read and write the samples.
1015
   * @private
1016
   */
1017
  updateDataType_() {
1018
    this.dataType = {
1019
      bits: ((parseInt(this.bitDepth, 10) - 1) | 7) + 1,
1020
      fp: this.bitDepth == '32f' || this.bitDepth == '64',
1021
      signed: this.bitDepth != '8',
1022
      be: this.container == 'RIFX'
1023
    };
1024
    if (['4', '8a', '8m'].indexOf(this.bitDepth) > -1 ) {
1025
      this.dataType.bits = 8;
1026
      this.dataType.signed = false;
1027
    }
1028
  }
1029
1030
  /**
1031
   * Return 'RIFF' if the container is 'RF64', the current container name
1032
   * otherwise. Used to enforce 'RIFF' when RF64 is not allowed.
1033
   * @return {string}
1034
   * @private
1035
   */
1036
  correctContainer_() {
1037
    return this.container == 'RF64' ? 'RIFF' : this.container;
1038
  }
1039
1040
  /**
1041
   * Truncate float samples on over and underflow.
1042
   * @private
1043
   */
1044
  truncateSamples_(samples) {
1045
    for (let i = 0, len = samples.length; i < len; i++) {
1046
      if (samples[i] > 1) {
1047
        samples[i] = 1;
1048
      } else if (samples[i] < -1) {
1049
        samples[i] = -1;
1050
      }
1051
    }
1052
  }
1053
1054
  /**
1055
   * Return a .wav file byte buffer with the data from the WaveFile object.
1056
   * The return value of this method can be written straight to disk.
1057
   * @return {!Uint8Array} The wav file bytes.
1058
   * @private
1059
   */
1060
  writeWavBuffer() {
1061
    this.uInt16_.be = this.container === 'RIFX';
1062
    this.uInt32_.be = this.uInt16_.be;
1063
    /** @type {!Array<!Array<number>>} */
1064
    let fileBody = [
1065
      this.getJunkBytes_(),
1066
      this.getDs64Bytes_(),
1067
      this.getBextBytes_(),
1068
      this.getFmtBytes_(),
1069
      this.getFactBytes_(),
1070
      packString(this.data.chunkId),
1071
      pack(this.data.samples.length, this.uInt32_),
1072
      this.data.samples,
1073
      this.getCueBytes_(),
1074
      this.getSmplBytes_(),
1075
      this.getLISTBytes_()
1076
    ];
1077
    /** @type {number} */
1078
    let fileBodyLength = 0;
1079
    for (let i=0; i<fileBody.length; i++) {
1080
      fileBodyLength += fileBody[i].length;
1081
    }
1082
    /** @type {!Uint8Array} */
1083
    let file = new Uint8Array(fileBodyLength + 12);
1084
    /** @type {number} */
1085
    let index = 0;
1086
    index = packStringTo(this.container, file, index);
1087
    index = packTo(fileBodyLength + 4, this.uInt32_, file, index);
1088
    index = packStringTo(this.format, file, index);
1089
    for (let i=0; i<fileBody.length; i++) {
1090
      file.set(fileBody[i], index);
1091
      index += fileBody[i].length;
1092
    }
1093
    return file;
1094
  }
1095
1096
  /**
1097
   * Return the bytes of the 'bext' chunk.
1098
   * @private
1099
   */
1100
  getBextBytes_() {
1101
    /** @type {!Array<number>} */
1102
    let bytes = [];
1103
    this.enforceBext_();
1104
    if (this.bext.chunkId) {
1105
      this.bext.chunkSize = 602 + this.bext.codingHistory.length;
1106
      bytes = bytes.concat(
1107
        packString(this.bext.chunkId),
1108
        pack(602 + this.bext.codingHistory.length, this.uInt32_),
1109
        writeString(this.bext.description, 256),
1110
        writeString(this.bext.originator, 32),
1111
        writeString(this.bext.originatorReference, 32),
1112
        writeString(this.bext.originationDate, 10),
1113
        writeString(this.bext.originationTime, 8),
1114
        pack(this.bext.timeReference[0], this.uInt32_),
1115
        pack(this.bext.timeReference[1], this.uInt32_),
1116
        pack(this.bext.version, this.uInt16_),
1117
        writeString(this.bext.UMID, 64),
1118
        pack(this.bext.loudnessValue, this.uInt16_),
1119
        pack(this.bext.loudnessRange, this.uInt16_),
1120
        pack(this.bext.maxTruePeakLevel, this.uInt16_),
1121
        pack(this.bext.maxMomentaryLoudness, this.uInt16_),
1122
        pack(this.bext.maxShortTermLoudness, this.uInt16_),
1123
        writeString(this.bext.reserved, 180),
1124
        writeString(
1125
          this.bext.codingHistory, this.bext.codingHistory.length));
1126
    }
1127
    return bytes;
1128
  }
1129
1130
  /**
1131
   * Make sure a 'bext' chunk is created if BWF data was created in a file.
1132
   * @private
1133
   */
1134
  enforceBext_() {
1135
    for (let prop in this.bext) {
1136
      if (this.bext.hasOwnProperty(prop)) {
1137
        if (this.bext[prop] && prop != 'timeReference') {
1138
          this.bext.chunkId = 'bext';
1139
          break;
1140
        }
1141
      }
1142
    }
1143
    if (this.bext.timeReference[0] || this.bext.timeReference[1]) {
1144
      this.bext.chunkId = 'bext';
1145
    }
1146
  }
1147
1148
  /**
1149
   * Return the bytes of the 'ds64' chunk.
1150
   * @return {!Array<number>} The 'ds64' chunk bytes.
1151
   * @private
1152
   */
1153
  getDs64Bytes_() {
1154
    /** @type {!Array<number>} */
1155
    let bytes = [];
1156
    if (this.ds64.chunkId) {
1157
      bytes = bytes.concat(
1158
        packString(this.ds64.chunkId),
1159
        pack(this.ds64.chunkSize, this.uInt32_),
1160
        pack(this.ds64.riffSizeHigh, this.uInt32_),
1161
        pack(this.ds64.riffSizeLow, this.uInt32_),
1162
        pack(this.ds64.dataSizeHigh, this.uInt32_),
1163
        pack(this.ds64.dataSizeLow, this.uInt32_),
1164
        pack(this.ds64.originationTime, this.uInt32_),
1165
        pack(this.ds64.sampleCountHigh, this.uInt32_),
1166
        pack(this.ds64.sampleCountLow, this.uInt32_));
1167
    }
1168
    //if (this.ds64.tableLength) {
1169
    //  ds64Bytes = ds64Bytes.concat(
1170
    //    pack(this.ds64.tableLength, this.uInt32_),
1171
    //    this.ds64.table);
1172
    //}
1173
    return bytes;
1174
  }
1175
1176
  /**
1177
   * Return the bytes of the 'cue ' chunk.
1178
   * @return {!Array<number>} The 'cue ' chunk bytes.
1179
   * @private
1180
   */
1181
  getCueBytes_() {
1182
    /** @type {!Array<number>} */
1183
    let bytes = [];
1184
    if (this.cue.chunkId) {
1185
      /** @type {!Array<number>} */
1186
      let cuePointsBytes = this.getCuePointsBytes_();
1187
      bytes = bytes.concat(
1188
        packString(this.cue.chunkId),
1189
        pack(cuePointsBytes.length + 4, this.uInt32_),
1190
        pack(this.cue.dwCuePoints, this.uInt32_),
1191
        cuePointsBytes);
1192
    }
1193
    return bytes;
1194
  }
1195
1196
  /**
1197
   * Return the bytes of the 'cue ' points.
1198
   * @return {!Array<number>} The 'cue ' points as an array of bytes.
1199
   * @private
1200
   */
1201
  getCuePointsBytes_() {
1202
    /** @type {!Array<number>} */
1203
    let points = [];
1204
    for (let i=0; i<this.cue.dwCuePoints; i++) {
1205
      points = points.concat(
1206
        pack(this.cue.points[i].dwName, this.uInt32_),
1207
        pack(this.cue.points[i].dwPosition, this.uInt32_),
1208
        packString(this.cue.points[i].fccChunk),
1209
        pack(this.cue.points[i].dwChunkStart, this.uInt32_),
1210
        pack(this.cue.points[i].dwBlockStart, this.uInt32_),
1211
        pack(this.cue.points[i].dwSampleOffset, this.uInt32_));
1212
    }
1213
    return points;
1214
  }
1215
1216
  /**
1217
   * Return the bytes of the 'smpl' chunk.
1218
   * @return {!Array<number>} The 'smpl' chunk bytes.
1219
   * @private
1220
   */
1221
  getSmplBytes_() {
1222
    /** @type {!Array<number>} */
1223
    let bytes = [];
1224
    if (this.smpl.chunkId) {
1225
      /** @type {!Array<number>} */
1226
      let smplLoopsBytes = this.getSmplLoopsBytes_();
1227
      bytes = bytes.concat(
1228
        packString(this.smpl.chunkId),
1229
        pack(smplLoopsBytes.length + 36, this.uInt32_),
1230
        pack(this.smpl.dwManufacturer, this.uInt32_),
1231
        pack(this.smpl.dwProduct, this.uInt32_),
1232
        pack(this.smpl.dwSamplePeriod, this.uInt32_),
1233
        pack(this.smpl.dwMIDIUnityNote, this.uInt32_),
1234
        pack(this.smpl.dwMIDIPitchFraction, this.uInt32_),
1235
        pack(this.smpl.dwSMPTEFormat, this.uInt32_),
1236
        pack(this.smpl.dwSMPTEOffset, this.uInt32_),
1237
        pack(this.smpl.dwNumSampleLoops, this.uInt32_),
1238
        pack(this.smpl.dwSamplerData, this.uInt32_),
1239
        smplLoopsBytes);
1240
    }
1241
    return bytes;
1242
  }
1243
1244
  /**
1245
   * Return the bytes of the 'smpl' loops.
1246
   * @return {!Array<number>} The 'smpl' loops as an array of bytes.
1247
   * @private
1248
   */
1249
  getSmplLoopsBytes_() {
1250
    /** @type {!Array<number>} */
1251
    let loops = [];
1252
    for (let i=0; i<this.smpl.dwNumSampleLoops; i++) {
1253
      loops = loops.concat(
1254
        pack(this.smpl.loops[i].dwName, this.uInt32_),
1255
        pack(this.smpl.loops[i].dwType, this.uInt32_),
1256
        pack(this.smpl.loops[i].dwStart, this.uInt32_),
1257
        pack(this.smpl.loops[i].dwEnd, this.uInt32_),
1258
        pack(this.smpl.loops[i].dwFraction, this.uInt32_),
1259
        pack(this.smpl.loops[i].dwPlayCount, this.uInt32_));
1260
    }
1261
    return loops;
1262
  }
1263
1264
  /**
1265
   * Return the bytes of the 'fact' chunk.
1266
   * @return {!Array<number>} The 'fact' chunk bytes.
1267
   * @private
1268
   */
1269
  getFactBytes_() {
1270
    /** @type {!Array<number>} */
1271
    let bytes = [];
1272
    if (this.fact.chunkId) {
1273
      bytes = bytes.concat(
1274
        packString(this.fact.chunkId),
1275
        pack(this.fact.chunkSize, this.uInt32_),
1276
        pack(this.fact.dwSampleLength, this.uInt32_));
1277
    }
1278
    return bytes;
1279
  }
1280
1281
  /**
1282
   * Return the bytes of the 'fmt ' chunk.
1283
   * @return {!Array<number>} The 'fmt' chunk bytes.
1284
   * @throws {Error} if no 'fmt ' chunk is present.
1285
   * @private
1286
   */
1287
  getFmtBytes_() {
1288
    /** @type {!Array<number>} */
1289
    let fmtBytes = [];
1290
    if (this.fmt.chunkId) {
1291
      return fmtBytes.concat(
1292
        packString(this.fmt.chunkId),
1293
        pack(this.fmt.chunkSize, this.uInt32_),
1294
        pack(this.fmt.audioFormat, this.uInt16_),
1295
        pack(this.fmt.numChannels, this.uInt16_),
1296
        pack(this.fmt.sampleRate, this.uInt32_),
1297
        pack(this.fmt.byteRate, this.uInt32_),
1298
        pack(this.fmt.blockAlign, this.uInt16_),
1299
        pack(this.fmt.bitsPerSample, this.uInt16_),
1300
        this.getFmtExtensionBytes_());
1301
    }
1302
    throw Error('Could not find the "fmt " chunk');
1303
  }
1304
1305
  /**
1306
   * Return the bytes of the fmt extension fields.
1307
   * @return {!Array<number>} The fmt extension bytes.
1308
   * @private
1309
   */
1310
  getFmtExtensionBytes_() {
1311
    /** @type {!Array<number>} */
1312
    let extension = [];
1313
    if (this.fmt.chunkSize > 16) {
1314
      extension = extension.concat(
1315
        pack(this.fmt.cbSize, this.uInt16_));
1316
    }
1317
    if (this.fmt.chunkSize > 18) {
1318
      extension = extension.concat(
1319
        pack(this.fmt.validBitsPerSample, this.uInt16_));
1320
    }
1321
    if (this.fmt.chunkSize > 20) {
1322
      extension = extension.concat(
1323
        pack(this.fmt.dwChannelMask, this.uInt32_));
1324
    }
1325
    if (this.fmt.chunkSize > 24) {
1326
      extension = extension.concat(
1327
        pack(this.fmt.subformat[0], this.uInt32_),
1328
        pack(this.fmt.subformat[1], this.uInt32_),
1329
        pack(this.fmt.subformat[2], this.uInt32_),
1330
        pack(this.fmt.subformat[3], this.uInt32_));
1331
    }
1332
    return extension;
1333
  }
1334
1335
  /**
1336
   * Return the bytes of the 'LIST' chunk.
1337
   * @return {!Array<number>} The 'LIST' chunk bytes.
1338
   */
1339
  getLISTBytes_() {
1340
    /** @type {!Array<number>} */
1341
    let bytes = [];
1342
    for (let i=0; i<this.LIST.length; i++) {
1343
      /** @type {!Array<number>} */
1344
      let subChunksBytes = this.getLISTSubChunksBytes_(
1345
          this.LIST[i].subChunks, this.LIST[i].format);
1346
      bytes = bytes.concat(
1347
        packString(this.LIST[i].chunkId),
1348
        pack(subChunksBytes.length + 4, this.uInt32_),
1349
        packString(this.LIST[i].format),
1350
        subChunksBytes);
1351
    }
1352
    return bytes;
1353
  }
1354
1355
  /**
1356
   * Return the bytes of the sub chunks of a 'LIST' chunk.
1357
   * @param {!Array<!Object>} subChunks The 'LIST' sub chunks.
1358
   * @param {string} format The format of the 'LIST' chunk.
1359
   *    Currently supported values are 'adtl' or 'INFO'.
1360
   * @return {!Array<number>} The sub chunk bytes.
1361
   * @private
1362
   */
1363
  getLISTSubChunksBytes_(subChunks, format) {
1364
    /** @type {!Array<number>} */
1365
    let bytes = [];
1366
    for (let i=0; i<subChunks.length; i++) {
1367
      if (format == 'INFO') {
1368
        bytes = bytes.concat(
1369
          packString(subChunks[i].chunkId),
1370
          pack(subChunks[i].value.length + 1, this.uInt32_),
1371
          writeString(
1372
            subChunks[i].value, subChunks[i].value.length));
1373
        bytes.push(0);
1374
      } else if (format == 'adtl') {
1375
        if (['labl', 'note'].indexOf(subChunks[i].chunkId) > -1) {
1376
          bytes = bytes.concat(
1377
            packString(subChunks[i].chunkId),
1378
            pack(
1379
              subChunks[i].value.length + 4 + 1, this.uInt32_),
1380
            pack(subChunks[i].dwName, this.uInt32_),
1381
            writeString(
1382
              subChunks[i].value,
1383
              subChunks[i].value.length));
1384
          bytes.push(0);
1385
        } else if (subChunks[i].chunkId == 'ltxt') {
1386
          bytes = bytes.concat(
1387
            this.getLtxtChunkBytes_(subChunks[i]));
1388
        }
1389
      }
1390
      if (bytes.length % 2) {
1391
        bytes.push(0);
1392
      }
1393
    }
1394
    return bytes;
1395
  }
1396
1397
  /**
1398
   * Return the bytes of a 'ltxt' chunk.
1399
   * @param {!Object} ltxt the 'ltxt' chunk.
1400
   * @private
1401
   */
1402
  getLtxtChunkBytes_(ltxt) {
1403
    return [].concat(
1404
      packString(ltxt.chunkId),
1405
      pack(ltxt.value.length + 20, this.uInt32_),
1406
      pack(ltxt.dwName, this.uInt32_),
1407
      pack(ltxt.dwSampleLength, this.uInt32_),
1408
      pack(ltxt.dwPurposeID, this.uInt32_),
1409
      pack(ltxt.dwCountry, this.uInt16_),
1410
      pack(ltxt.dwLanguage, this.uInt16_),
1411
      pack(ltxt.dwDialect, this.uInt16_),
1412
      pack(ltxt.dwCodePage, this.uInt16_),
1413
      writeString(ltxt.value, ltxt.value.length));
1414
  }
1415
1416
  /**
1417
   * Return the bytes of the 'junk' chunk.
1418
   * @private
1419
   */
1420
  getJunkBytes_() {
1421
    /** @type {!Array<number>} */
1422
    let bytes = [];
1423
    if (this.junk.chunkId) {
1424
      return bytes.concat(
1425
        packString(this.junk.chunkId),
1426
        pack(this.junk.chunkData.length, this.uInt32_),
1427
        this.junk.chunkData);
1428
    }
1429
    return bytes;
1430
  }
1431
1432
  /**
1433
   * Set up the WaveFile object from a byte buffer.
1434
   * @param {!Uint8Array} wavBuffer The buffer.
1435
   * @param {boolean} samples True if the samples should be loaded.
1436
   * @throws {Error} If container is not RIFF, RIFX or RF64.
1437
   * @throws {Error} If format is not WAVE.
1438
   * @throws {Error} If no 'fmt ' chunk is found.
1439
   * @throws {Error} If no 'data' chunk is found.
1440
   */
1441
  readWavBuffer(wavBuffer, samples) {
1442
    this.head_ = 0;
1443
    this.readRIFFChunk_(wavBuffer);
1444
    if (this.format != 'WAVE') {
1445
      throw Error('Could not find the "WAVE" format identifier');
1446
    }
1447
    this.getSignature_(wavBuffer);
1448
    this.readDs64Chunk_(wavBuffer);
1449
    this.readFmtChunk_(wavBuffer);
1450
    this.readFactChunk_(wavBuffer);
1451
    this.readBextChunk_(wavBuffer);
1452
    this.readCueChunk_(wavBuffer);
1453
    this.readSmplChunk_(wavBuffer);
1454
    this.readDataChunk_(wavBuffer, samples);
1455
    this.readJunkChunk_(wavBuffer);
1456
    this.readLISTChunk_(wavBuffer);
1457
  }
1458
1459
  /**
1460
   * Read the 'fmt ' chunk of a wave file.
1461
   * @param {!Uint8Array} buffer The wav file buffer.
1462
   * @throws {Error} If no 'fmt ' chunk is found.
1463
   * @private
1464
   */
1465
  readFmtChunk_(buffer) {
1466
    /** @type {?Object} */
1467
    let chunk = this.findChunk_('fmt ');
1468
    if (chunk) {
1469
      this.head_ = chunk.chunkData.start;
1470
      this.fmt.chunkId = chunk.chunkId;
1471
      this.fmt.chunkSize = chunk.chunkSize;
1472
      this.fmt.audioFormat = this.read_(buffer, this.uInt16_);
1473
      this.fmt.numChannels = this.read_(buffer, this.uInt16_);
1474
      this.fmt.sampleRate = this.read_(buffer, this.uInt32_);
1475
      this.fmt.byteRate = this.read_(buffer, this.uInt32_);
1476
      this.fmt.blockAlign = this.read_(buffer, this.uInt16_);
1477
      this.fmt.bitsPerSample = this.read_(buffer, this.uInt16_);
1478
      this.readFmtExtension_(buffer);
1479
    } else {
1480
      throw Error('Could not find the "fmt " chunk');
1481
    }
1482
  }
1483
1484
  /**
1485
   * Read the 'fmt ' chunk extension.
1486
   * @param {!Uint8Array} buffer The wav file buffer.
1487
   * @private
1488
   */
1489
  readFmtExtension_(buffer) {
1490
    if (this.fmt.chunkSize > 16) {
1491
      this.fmt.cbSize = this.read_(buffer, this.uInt16_);
1492
      if (this.fmt.chunkSize > 18) {
1493
        this.fmt.validBitsPerSample = this.read_(buffer, this.uInt16_);
1494
        if (this.fmt.chunkSize > 20) {
1495
          this.fmt.dwChannelMask = this.read_(buffer, this.uInt32_);
1496
          this.fmt.subformat = [
1497
            this.read_(buffer, this.uInt32_),
1498
            this.read_(buffer, this.uInt32_),
1499
            this.read_(buffer, this.uInt32_),
1500
            this.read_(buffer, this.uInt32_)];
1501
        }
1502
      }
1503
    }
1504
  }
1505
1506
  /**
1507
   * Read the 'fact' chunk of a wav file.
1508
   * @param {!Uint8Array} buffer The wav file buffer.
1509
   * @private
1510
   */
1511
  readFactChunk_(buffer) {
1512
    /** @type {?Object} */
1513
    let chunk = this.findChunk_('fact');
1514
    if (chunk) {
1515
      this.head_ = chunk.chunkData.start;
1516
      this.fact.chunkId = chunk.chunkId;
1517
      this.fact.chunkSize = chunk.chunkSize;
1518
      this.fact.dwSampleLength = this.read_(buffer, this.uInt32_);
1519
    }
1520
  }
1521
1522
  /**
1523
   * Read the 'cue ' chunk of a wave file.
1524
   * @param {!Uint8Array} buffer The wav file buffer.
1525
   * @private
1526
   */
1527
  readCueChunk_(buffer) {
1528
    /** @type {?Object} */
1529
    let chunk = this.findChunk_('cue ');
1530
    if (chunk) {
1531
      this.head_ = chunk.chunkData.start;
1532
      this.cue.chunkId = chunk.chunkId;
1533
      this.cue.chunkSize = chunk.chunkSize;
1534
      this.cue.dwCuePoints = this.read_(buffer, this.uInt32_);
1535
      for (let i = 0; i < this.cue.dwCuePoints; i++) {
1536
        this.cue.points.push({
1537
          dwName: this.read_(buffer, this.uInt32_),
1538
          dwPosition: this.read_(buffer, this.uInt32_),
1539
          fccChunk: this.readString_(buffer, 4),
1540
          dwChunkStart: this.read_(buffer, this.uInt32_),
1541
          dwBlockStart: this.read_(buffer, this.uInt32_),
1542
          dwSampleOffset: this.read_(buffer, this.uInt32_),
1543
        });
1544
      }
1545
    }
1546
  }
1547
1548
  /**
1549
   * Read the 'smpl' chunk of a wave file.
1550
   * @param {!Uint8Array} buffer The wav file buffer.
1551
   * @private
1552
   */
1553
  readSmplChunk_(buffer) {
1554
    /** @type {?Object} */
1555
    let chunk = this.findChunk_('smpl');
1556
    if (chunk) {
1557
      this.head_ = chunk.chunkData.start;
1558
      this.smpl.chunkId = chunk.chunkId;
1559
      this.smpl.chunkSize = chunk.chunkSize;
1560
      this.smpl.dwManufacturer = this.read_(buffer, this.uInt32_);
1561
      this.smpl.dwProduct = this.read_(buffer, this.uInt32_);
1562
      this.smpl.dwSamplePeriod = this.read_(buffer, this.uInt32_);
1563
      this.smpl.dwMIDIUnityNote = this.read_(buffer, this.uInt32_);
1564
      this.smpl.dwMIDIPitchFraction = this.read_(buffer, this.uInt32_);
1565
      this.smpl.dwSMPTEFormat = this.read_(buffer, this.uInt32_);
1566
      this.smpl.dwSMPTEOffset = this.read_(buffer, this.uInt32_);
1567
      this.smpl.dwNumSampleLoops = this.read_(buffer, this.uInt32_);
1568
      this.smpl.dwSamplerData = this.read_(buffer, this.uInt32_);
1569
      for (let i = 0; i < this.smpl.dwNumSampleLoops; i++) {
1570
        this.smpl.loops.push({
1571
          dwName: this.read_(buffer, this.uInt32_),
1572
          dwType: this.read_(buffer, this.uInt32_),
1573
          dwStart: this.read_(buffer, this.uInt32_),
1574
          dwEnd: this.read_(buffer, this.uInt32_),
1575
          dwFraction: this.read_(buffer, this.uInt32_),
1576
          dwPlayCount: this.read_(buffer, this.uInt32_),
1577
        });
1578
      }
1579
    }
1580
  }
1581
1582
  /**
1583
   * Read the 'data' chunk of a wave file.
1584
   * @param {!Uint8Array} buffer The wav file buffer.
1585
   * @param {boolean} samples True if the samples should be loaded.
1586
   * @throws {Error} If no 'data' chunk is found.
1587
   * @private
1588
   */
1589
  readDataChunk_(buffer, samples) {
1590
    /** @type {?Object} */
1591
    let chunk = this.findChunk_('data');
1592
    if (chunk) {
1593
      this.data.chunkId = 'data';
1594
      this.data.chunkSize = chunk.chunkSize;
1595
      if (samples) {
1596
        this.data.samples = buffer.slice(
1597
          chunk.chunkData.start,
1598
          chunk.chunkData.end);
1599
      }
1600
    } else {
1601
      throw Error('Could not find the "data" chunk');
1602
    }
1603
  }
1604
1605
  /**
1606
   * Read the 'bext' chunk of a wav file.
1607
   * @param {!Uint8Array} buffer The wav file buffer.
1608
   * @private
1609
   */
1610
  readBextChunk_(buffer) {
1611
    /** @type {?Object} */
1612
    let chunk = this.findChunk_('bext');
1613
    if (chunk) {
1614
      this.head_ = chunk.chunkData.start;
1615
      this.bext.chunkId = chunk.chunkId;
1616
      this.bext.chunkSize = chunk.chunkSize;
1617
      this.bext.description = this.readString_(buffer, 256);
1618
      this.bext.originator = this.readString_(buffer, 32);
1619
      this.bext.originatorReference = this.readString_(buffer, 32);
1620
      this.bext.originationDate = this.readString_(buffer, 10);
1621
      this.bext.originationTime = this.readString_(buffer, 8);
1622
      this.bext.timeReference = [
1623
        this.read_(buffer, this.uInt32_),
1624
        this.read_(buffer, this.uInt32_)];
1625
      this.bext.version = this.read_(buffer, this.uInt16_);
1626
      this.bext.UMID = this.readString_(buffer, 64);
1627
      this.bext.loudnessValue = this.read_(buffer, this.uInt16_);
1628
      this.bext.loudnessRange = this.read_(buffer, this.uInt16_);
1629
      this.bext.maxTruePeakLevel = this.read_(buffer, this.uInt16_);
1630
      this.bext.maxMomentaryLoudness = this.read_(buffer, this.uInt16_);
1631
      this.bext.maxShortTermLoudness = this.read_(buffer, this.uInt16_);
1632
      this.bext.reserved = this.readString_(buffer, 180);
1633
      this.bext.codingHistory = this.readString_(
1634
        buffer, this.bext.chunkSize - 602);
1635
    }
1636
  }
1637
1638
  /**
1639
   * Read the 'ds64' chunk of a wave file.
1640
   * @param {!Uint8Array} buffer The wav file buffer.
1641
   * @throws {Error} If no 'ds64' chunk is found and the file is RF64.
1642
   * @private
1643
   */
1644
  readDs64Chunk_(buffer) {
1645
    /** @type {?Object} */
1646
    let chunk = this.findChunk_('ds64');
1647
    if (chunk) {
1648
      this.head_ = chunk.chunkData.start;
1649
      this.ds64.chunkId = chunk.chunkId;
1650
      this.ds64.chunkSize = chunk.chunkSize;
1651
      this.ds64.riffSizeHigh = this.read_(buffer, this.uInt32_);
1652
      this.ds64.riffSizeLow = this.read_(buffer, this.uInt32_);
1653
      this.ds64.dataSizeHigh = this.read_(buffer, this.uInt32_);
1654
      this.ds64.dataSizeLow = this.read_(buffer, this.uInt32_);
1655
      this.ds64.originationTime = this.read_(buffer, this.uInt32_);
1656
      this.ds64.sampleCountHigh = this.read_(buffer, this.uInt32_);
1657
      this.ds64.sampleCountLow = this.read_(buffer, this.uInt32_);
1658
      //if (wav.ds64.chunkSize > 28) {
1659
      //  wav.ds64.tableLength = unpack(
1660
      //    chunkData.slice(28, 32), uInt32_);
1661
      //  wav.ds64.table = chunkData.slice(
1662
      //     32, 32 + wav.ds64.tableLength);
1663
      //}
1664
    } else {
1665
      if (this.container == 'RF64') {
1666
        throw Error('Could not find the "ds64" chunk');
1667
      }
1668
    }
1669
  }
1670
1671
  /**
1672
   * Read the 'LIST' chunks of a wave file.
1673
   * @param {!Uint8Array} buffer The wav file buffer.
1674
   * @private
1675
   */
1676
  readLISTChunk_(buffer) {
1677
    /** @type {?Object} */
1678
    let listChunks = this.findChunk_('LIST', true);
1679
    if (listChunks !== null) {
1680
      for (let j=0; j < listChunks.length; j++) {
1681
        /** @type {!Object} */
1682
        let subChunk = listChunks[j];
1683
        this.LIST.push({
1684
          chunkId: subChunk.chunkId,
1685
          chunkSize: subChunk.chunkSize,
1686
          format: subChunk.format,
1687
          subChunks: []});
1688
        for (let x=0; x<subChunk.subChunks.length; x++) {
1689
          this.readLISTSubChunks_(subChunk.subChunks[x],
1690
            subChunk.format, buffer);
1691
        }
1692
      }
1693
    }
1694
  }
1695
1696
  /**
1697
   * Read the sub chunks of a 'LIST' chunk.
1698
   * @param {!Object} subChunk The 'LIST' subchunks.
1699
   * @param {string} format The 'LIST' format, 'adtl' or 'INFO'.
1700
   * @param {!Uint8Array} buffer The wav file buffer.
1701
   * @private
1702
   */
1703
  readLISTSubChunks_(subChunk, format, buffer) {
1704
    if (format == 'adtl') {
1705
      if (['labl', 'note','ltxt'].indexOf(subChunk.chunkId) > -1) {
1706
        this.head_ = subChunk.chunkData.start;
1707
        /** @type {!Object<string, string|number>} */
1708
        let item = {
1709
          chunkId: subChunk.chunkId,
1710
          chunkSize: subChunk.chunkSize,
1711
          dwName: this.read_(buffer, this.uInt32_)
1712
        };
1713
        if (subChunk.chunkId == 'ltxt') {
1714
          item.dwSampleLength = this.read_(buffer, this.uInt32_);
1715
          item.dwPurposeID = this.read_(buffer, this.uInt32_);
1716
          item.dwCountry = this.read_(buffer, this.uInt16_);
1717
          item.dwLanguage = this.read_(buffer, this.uInt16_);
1718
          item.dwDialect = this.read_(buffer, this.uInt16_);
1719
          item.dwCodePage = this.read_(buffer, this.uInt16_);
1720
        }
1721
        item.value = this.readZSTR_(buffer, this.head_);
1722
        this.LIST[this.LIST.length - 1].subChunks.push(item);
1723
      }
1724
    // RIFF INFO tags like ICRD, ISFT, ICMT
1725
    } else if(format == 'INFO') {
1726
      this.head_ = subChunk.chunkData.start;
1727
      this.LIST[this.LIST.length - 1].subChunks.push({
1728
        chunkId: subChunk.chunkId,
1729
        chunkSize: subChunk.chunkSize,
1730
        value: this.readZSTR_(buffer, this.head_)
1731
      });
1732
    }
1733
  }
1734
1735
  /**
1736
   * Read the 'junk' chunk of a wave file.
1737
   * @param {!Uint8Array} buffer The wav file buffer.
1738
   * @private
1739
   */
1740
  readJunkChunk_(buffer) {
1741
    /** @type {?Object} */
1742
    let chunk = this.findChunk_('junk');
1743
    if (chunk) {
1744
      this.junk = {
1745
        chunkId: chunk.chunkId,
1746
        chunkSize: chunk.chunkSize,
1747
        chunkData: [].slice.call(buffer.slice(
1748
          chunk.chunkData.start,
1749
          chunk.chunkData.end))
1750
      };
1751
    }
1752
  }
1753
1754
  /**
1755
   * Define the header of a wav file.
1756
   * @param {string} bitDepthCode The audio bit depth
1757
   * @param {number} numChannels The number of channels
1758
   * @param {number} sampleRate The sample rate.
1759
   * @param {number} numBytes The number of bytes each sample use.
1760
   * @param {number} samplesLength The length of the samples in bytes.
1761
   * @param {!Object} options The extra options, like container defintion.
1762
   * @private
1763
   */
1764
  makeWavHeader(
1765
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1766
    if (bitDepthCode == '4') {
1767
      this.createADPCMHeader_(
1768
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1769
1770
    } else if (bitDepthCode == '8a' || bitDepthCode == '8m') {
1771
      this.createALawMulawHeader_(
1772
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1773
1774
    } else if(Object.keys(this.WAV_AUDIO_FORMATS).indexOf(bitDepthCode) == -1 ||
1775
        numChannels > 2) {
1776
      this.createExtensibleHeader_(
1777
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1778
1779
    } else {
1780
      this.createPCMHeader_(
1781
        bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);      
1782
    }
1783
  }
1784
1785
  /**
1786
   * Create the header of a linear PCM wave file.
1787
   * @param {string} bitDepthCode The audio bit depth
1788
   * @param {number} numChannels The number of channels
1789
   * @param {number} sampleRate The sample rate.
1790
   * @param {number} numBytes The number of bytes each sample use.
1791
   * @param {number} samplesLength The length of the samples in bytes.
1792
   * @param {!Object} options The extra options, like container defintion.
1793
   * @private
1794
   */
1795
  createPCMHeader_(
1796
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1797
    this.container = options.container;
1798
    this.chunkSize = 36 + samplesLength;
1799
    this.format = 'WAVE';
1800
    this.bitDepth = bitDepthCode;
1801
    this.fmt = {
1802
      chunkId: 'fmt ',
1803
      chunkSize: 16,
1804
      audioFormat: this.WAV_AUDIO_FORMATS[bitDepthCode] || 65534,
1805
      numChannels: numChannels,
1806
      sampleRate: sampleRate,
1807
      byteRate: (numChannels * numBytes) * sampleRate,
1808
      blockAlign: numChannels * numBytes,
1809
      bitsPerSample: parseInt(bitDepthCode, 10),
1810
      cbSize: 0,
1811
      validBitsPerSample: 0,
1812
      dwChannelMask: 0,
1813
      subformat: []
1814
    };
1815
  }
1816
1817
  /**
1818
   * Create the header of a ADPCM wave file.
1819
   * @param {string} bitDepthCode The audio bit depth
1820
   * @param {number} numChannels The number of channels
1821
   * @param {number} sampleRate The sample rate.
1822
   * @param {number} numBytes The number of bytes each sample use.
1823
   * @param {number} samplesLength The length of the samples in bytes.
1824
   * @param {!Object} options The extra options, like container defintion.
1825
   * @private
1826
   */
1827
  createADPCMHeader_(
1828
    bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1829
    this.createPCMHeader_(
1830
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1831
    this.chunkSize = 40 + samplesLength;
1832
    this.fmt.chunkSize = 20;
1833
    this.fmt.byteRate = 4055;
1834
    this.fmt.blockAlign = 256;
1835
    this.fmt.bitsPerSample = 4;
1836
    this.fmt.cbSize = 2;
1837
    this.fmt.validBitsPerSample = 505;
1838
    this.fact = {
1839
      chunkId: 'fact',
1840
      chunkSize: 4,
1841
      dwSampleLength: samplesLength * 2
1842
    };
1843
  }
1844
1845
  /**
1846
   * Create the header of WAVE_FORMAT_EXTENSIBLE file.
1847
   * @param {string} bitDepthCode The audio bit depth
1848
   * @param {number} numChannels The number of channels
1849
   * @param {number} sampleRate The sample rate.
1850
   * @param {number} numBytes The number of bytes each sample use.
1851
   * @param {number} samplesLength The length of the samples in bytes.
1852
   * @param {!Object} options The extra options, like container defintion.
1853
   * @private
1854
   */
1855
  createExtensibleHeader_(
1856
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1857
    this.createPCMHeader_(
1858
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1859
    this.chunkSize = 36 + 24 + samplesLength;
1860
    this.fmt.chunkSize = 40;
1861
    this.fmt.bitsPerSample = ((parseInt(bitDepthCode, 10) - 1) | 7) + 1;
1862
    this.fmt.cbSize = 22;
1863
    this.fmt.validBitsPerSample = parseInt(bitDepthCode, 10);
1864
    this.fmt.dwChannelMask = dwChannelMask(numChannels);
1865
    // subformat 128-bit GUID as 4 32-bit values
1866
    // only supports uncompressed integer PCM samples
1867
    this.fmt.subformat = [1, 1048576, 2852126848, 1905997824];
1868
  }
1869
1870
  /**
1871
   * Create the header of mu-Law and A-Law wave files.
1872
   * @param {string} bitDepthCode The audio bit depth
1873
   * @param {number} numChannels The number of channels
1874
   * @param {number} sampleRate The sample rate.
1875
   * @param {number} numBytes The number of bytes each sample use.
1876
   * @param {number} samplesLength The length of the samples in bytes.
1877
   * @param {!Object} options The extra options, like container defintion.
1878
   * @private
1879
   */
1880
  createALawMulawHeader_(
1881
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options) {
1882
    this.createPCMHeader_(
1883
      bitDepthCode, numChannels, sampleRate, numBytes, samplesLength, options);
1884
    this.chunkSize = 40 + samplesLength;
1885
    this.fmt.chunkSize = 20;
1886
    this.fmt.cbSize = 2;
1887
    this.fmt.validBitsPerSample = 8;
1888
    this.fact = {
1889
      chunkId: 'fact',
1890
      chunkSize: 4,
1891
      dwSampleLength: samplesLength
1892
    };
1893
  }
1894
1895
  /**
1896
   * Validate the header of the file.
1897
   * @throws {Error} If any property of the object appears invalid.
1898
   * @private
1899
   */
1900
  validateWavHeader_() {
1901
    this.validateBitDepth_();
1902
    this.validateNumChannels_();
1903
    this.validateSampleRate_();
1904
  }
1905
1906
  /**
1907
   * Validate the bit depth.
1908
   * @return {boolean} True is the bit depth is valid.
1909
   * @throws {Error} If bit depth is invalid.
1910
   * @private
1911
   */
1912
  validateBitDepth_() {
1913
    if (!this.WAV_AUDIO_FORMATS[this.bitDepth]) {
1914
      if (parseInt(this.bitDepth, 10) > 8 &&
1915
          parseInt(this.bitDepth, 10) < 54) {
1916
        return true;
1917
      }
1918
      throw new Error('Invalid bit depth.');
1919
    }
1920
    return true;
1921
  }
1922
1923
  /**
1924
   * Validate the number of channels.
1925
   * @return {boolean} True is the number of channels is valid.
1926
   * @throws {Error} If the number of channels is invalid.
1927
   * @private
1928
   */
1929
  validateNumChannels_() {
1930
    /** @type {number} */
1931
    let blockAlign = this.fmt.numChannels * this.fmt.bitsPerSample / 8;
1932
    if (this.fmt.numChannels < 1 || blockAlign > 65535) {
1933
      throw new Error('Invalid number of channels.');
1934
    }
1935
    return true;
1936
  }
1937
1938
  /**
1939
   * Validate the sample rate value.
1940
   * @return {boolean} True is the sample rate is valid.
1941
   * @throws {Error} If the sample rate is invalid.
1942
   * @private
1943
   */
1944
  validateSampleRate_() {
1945
    /** @type {number} */
1946
    let byteRate = this.fmt.numChannels *
1947
      (this.fmt.bitsPerSample / 8) * this.fmt.sampleRate;
1948
    if (this.fmt.sampleRate < 1 || byteRate > 4294967295) {
1949
      throw new Error('Invalid sample rate.');
1950
    }
1951
    return true;
1952
  }
1953
}
1954